﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;

namespace Microsoft.Office.OneNote
{
    [Flags]
    public enum OneNotePropertyFlags : int
    {
        None = 0x0,
        RequiresGetValueCallback = 0x1,

        IsAttribute = 0x1000,
        IsElement = 0x2000,
    }

    public sealed class OneNoteProperty : IEquatable<OneNoteProperty>
    {
        private static readonly Object _syncRoot = new Object();
        private static readonly List<OneNoteProperty> _properties = new List<OneNoteProperty>();

        private readonly Type _type;
        private readonly Type _owner;
        private readonly XName _name;
        private readonly Int32 _globalIndex;
        private readonly Object _defaultValue;
        private readonly OneNotePropertyFlags _flags;

        private OneNoteProperty(Int32 globalIndex, XName name, Type propertyType, Type owner, Object defaultValue, OneNotePropertyFlags flags)
        {
            _name = name;
            _owner = owner;
            _flags = flags;
            _type = propertyType;
            _globalIndex = globalIndex;
            _defaultValue = defaultValue;
        }

        public XName Name
        {
            get { return _name; }
        }

        public Type OwnerType
        {
            get { return _owner; }
        }

        public Type PropertyType
        {
            get { return _type; }
        }

        public Object DefaultValue
        {
            get { return _defaultValue; }
        }

        public override int GetHashCode()
        {
            return _globalIndex;
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as OneNoteProperty);
        }

        public bool Equals(OneNoteProperty other)
        {
            if (other == null)
            {
                return false;
            }

            return _globalIndex.Equals(other._globalIndex);
        }

        public override string ToString()
        {
            return string.Concat(_owner.Name, ".", _name);
        }

        public bool CheckFlagsAnd(OneNotePropertyFlags flags)
        {
            return (_flags & flags) == flags;
        }

        public static OneNoteProperty Register(String name, Type propertyType, Type ownerType)
        {
            return Register(name, propertyType, ownerType, null);
        }

        public static OneNoteProperty Register(String name, Type propertyType, Type ownerType, OneNotePropertyFlags flags)
        {
            return Register(name, propertyType, ownerType, null, flags);
        }

        public static OneNoteProperty Register(String name, Type propertyType, Type ownerType, Object defaultValue)
        {
            return Register(name, propertyType, ownerType, defaultValue, OneNotePropertyFlags.None);
        }

        public static OneNoteProperty Register(String name, Type propertyType, Type ownerType, Object defaultValue, OneNotePropertyFlags flags)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            if (propertyType == null)
            {
                throw new ArgumentNullException("propertyType");
            }

            if (ownerType == null)
            {
                throw new ArgumentNullException("ownerType");
            }

            if (!typeof(OneNoteObject).IsAssignableFrom(ownerType))
            {
                throw new ArgumentException(ErrorMessages.InvalidPropertyOwnerType);
            }

            XName xname = XName.Get(name);
            OneNoteProperty property;

            lock (_syncRoot)
            {
                var existing = _properties.Find(p => p.Name.Equals(xname) && p.OwnerType == ownerType);

                if (existing != null)
                {
                    string message = String.Format(CultureInfo.CurrentUICulture,
                        ErrorMessages.PropertyAlreadyRegistered, xname, ownerType);

                    throw new InvalidOperationException(message);
                }

                if (OneNoteSchema.GetElementName(propertyType) != null)
                {
                    flags = OneNotePropertyFlags.IsElement | (flags & ~OneNotePropertyFlags.IsAttribute);
                }
                else
                {
                    flags = OneNotePropertyFlags.IsAttribute | (flags & ~OneNotePropertyFlags.IsElement);
                }

                property = new OneNoteProperty(_properties.Count, xname, propertyType, ownerType, defaultValue, flags);
                _properties.Add(property);
            }

            return property;
        }

        public static OneNoteProperty[] GetProperties(OneNoteObject obj)
        {
            return GetProperties(obj.GetType());
        }

        public static OneNoteProperty[] GetProperties(Type ownerType)
        {
            lock (_syncRoot)
            {
                return _properties.Where(p => p.OwnerType.IsAssignableFrom(ownerType)).ToArray();
            }
        }
    }
}
